/**
 * @file mpi_helper.h
 * @author Andreas Bok Andersen
 * @brief Helper class
 * Various functions used for MPI operations
 */
#define MPI_HELPER
#ifndef MPI_H
#include <mpi.h>
#define MPI_H
#endif

#ifndef CROP_CPP
#include "crop.cpp"
#endif

#ifndef ENTRY
#include "entry.h"
#endif

#include <stdio.h>
#include <vector>
#include <math.h>
#include <cmath>
#include "cmdline.h"
using namespace std;
using namespace boost;

#define INFILE "infile"
#define FORMAT "format"
#define KSIZE "ksize"
#define BSIZE "bsize"
#define STYPE "stype"

#define MPI_STATUS_MSG_COUNT -100
#define MPI_STATUS_MSG_BCAST_END -200
#define PAGESIZE 4096

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
	return SIZE;
}

#define DATADIR "../../data/"

struct Header {
	Header() {
	}
	;
	Header(int _TAG, int _count) :
			TAG(_TAG), count(_count) {
	}
	;
	int TAG;
	int count;
};
#ifndef COO_ITEM
struct COO_item {
	COO_item() {
	}
	;
	COO_item(int _i, int _j, float _w) :
	i(_i), j(_j), w(_w) {
	}
	;
	int i, j;
	float w;
};
#define COO_ITEM
#endif

#ifndef DATA_HEADER
struct DATA_header {
	int *JC, *CP, JC_SIZE, *COUNTS;
};
#define DATA_HEADER
#endif

#ifndef DCS_ITEM
typedef struct {
	int i;
	float w;
}DCS_item;
#define DCS_ITEM
#endif

static void create_MPI_Datatype(MPI_Datatype *datatype, int length, int stride, DCS_FORMAT type) {
	//fprintf(stderr, "creating DCS datatype \n");
	MPI_Type_create_hvector(2, length * sizeof(int), sizeof(int) * stride,
			MPI_BYTE, datatype);
	MPI_Type_commit(datatype);
}
static void create_MPI_Datatype(long int count, MPI_Datatype * newtype,DCS_FORMAT type) {
 MPI_Type_contiguous(2 * count, MPI_BYTE, newtype);
 MPI_Type_commit(newtype);
 }

static void create_MPI_Datatype(MPI_Datatype *datatype, int length, int stride, COO_FORMAT type) {
MPI_Type_create_hvector(3, length * sizeof(int), sizeof(int) * stride,
MPI_BYTE, datatype);
MPI_Type_commit(datatype);
}

static void create_MPI_Datatype(long int count, MPI_Datatype *newtype, COO_FORMAT type) {
MPI_Type_contiguous(3 * count, MPI_BYTE, newtype);
MPI_Type_commit(newtype);
}

static void ENTRY_TYPE(ENTRY_VEC * entries, MPI_Datatype * entrytype) {
	//printf("%s\n", "ENTRYTYPE: 0");

	MPI_Datatype types[5] = { MPI_INT, MPI_INT, MPI_INT64_T, MPI_FLOAT,
			MPI_FLOAT };
	int blocklen[5] = { 1, 1, 1, 1, 1 };
	MPI_Aint disp[5];
	MPI_Aint base;

	MPI_Get_address(&(*entries)[0], &base);
	MPI_Get_address(&(*entries)[0].i, &disp[0]);
	MPI_Get_address(&(*entries)[0].j, &disp[1]);
	MPI_Get_address(&(*entries)[0].hash, &disp[2]);
	MPI_Get_address(&(*entries)[0].weight, &disp[3]);
	MPI_Get_address(&(*entries)[0].eps, &disp[4]);

	for (int i = 0; i < 5; i++) {
		disp[i] = disp[i] - base;
	}
	//printf("%s\n", "ENTRYTYPE: 2");
	MPI_Type_create_struct(5, blocklen, disp, types, entrytype);
	//printf("%s\n", "ENTRYTYPE: 3");
	MPI_Type_commit(entrytype);
	//printf("%s\n", "ENTRYTYPE: 4");
}

static void ENTRY_TYPE(MPI_Datatype * entrytype) {
	//printf("%s\n", "ENTRYTYPE: 0");
	Entry entry;
	MPI_Datatype types[5] = { MPI_INT, MPI_INT, MPI_INT64_T, MPI_FLOAT,
			MPI_FLOAT };
	int blocklen[5] = { 1, 1, 1, 1, 1 };
	MPI_Aint disp[5];
	MPI_Aint base;

	MPI_Get_address(&entry, &base);
	MPI_Get_address(&entry.i, &disp[0]);
	MPI_Get_address(&entry.j, &disp[1]);
	MPI_Get_address(&entry.hash, &disp[2]);
	MPI_Get_address(&entry.weight, &disp[3]);
	MPI_Get_address(&entry.eps, &disp[4]);

	for (int i = 0; i < 5; i++) {
		disp[i] = disp[i] - base;
	}
	//printf("%s\n", "ENTRYTYPE: 2");
	MPI_Type_create_struct(5, blocklen, disp, types, entrytype);
	//printf("%s\n", "ENTRYTYPE: 3");
	MPI_Type_commit(entrytype);
	//printf("%s\n", "ENTRYTYPE: 4");

}

static void DCS_ENTRYTYPE(MPI_Datatype * entrytype) {
	DCS_item item;
	MPI_Datatype types[2] = { MPI_INT, MPI_FLOAT };
	int blocklen[2] = { 1, 1 };
	MPI_Aint disp[2], base;
	MPI_Get_address(&item, &base);
	MPI_Get_address(&item.i, &disp[0]);
	MPI_Get_address(&item.w, &disp[1]);
	disp[0] = disp[0] - base;
	disp[1] = disp[1] - base;

	MPI_Type_struct(2, blocklen, disp, types, entrytype);
	MPI_Type_commit(entrytype);
}

static void COO_ITEM_TYPE(MPI_Datatype * datatype) {
	COO_item * type = new COO_item();
	MPI_Datatype types[3] = { MPI_INT, MPI_INT, MPI_FLOAT };
	int blocklen[3] = { 1, 1, 1 };
	MPI_Aint disp[3], base;
	MPI_Get_address(&*type, &base);
	MPI_Get_address(&type->i, &disp[0]);
	MPI_Get_address(&type->j, &disp[1]);
	MPI_Get_address(&type->w, &disp[2]);
	for (int i = 0; i < 3; i++)
		disp[i] = disp[i] - base;
	MPI_Type_create_struct(3, blocklen, disp, types, datatype);
	MPI_Type_commit(datatype);
	delete type;
}

void merge_parallel(ENTRY_VEC * sum1, ENTRY_VEC * sum2, int sumsize) {

	printf("%s", "called merge_parallel\n");
	int sum1size = sum1->size();
	int sum2size = sum2->size();

	sort(sum1->begin(), sum1->end(), sort_by_key);

	sort(sum2->begin(), sum2->end(), sort_by_key);

	int i = 0, j = 0;

	printf("%s", "starting merge\n");

	while (i < sum1size and j < sum2size) {
		if (sum1->at(i) == sum2->at(j)) {
			sum1->at(i).weight += sum2->at(j).weight;
			sum1->at(i).eps += sum2->at(j).eps;
			i++;
			j++;
		} else {
			sum1->push_back(sum2->at(j));
			j++;
		}
	}

	sort(sum1->begin(), sum1->end(), sort_reverse);

	sum1->resize(min(sumsize, (int) sum1->size()));
}

/*
 * Parallel merging
 */
void merge_parallel(Entry * entries, int entrycount, ENTRY_VEC * summary,
		int k) {

	sort(entries, entries + entrycount, sort_by_key);

	ENTRY_P_SET * tempset = new ENTRY_P_SET();
	pair<ENTRY_P_SET::iterator, bool> result;
	for (int i = 0; i < entrycount; i++) {
		result = tempset->insert(&entries[i]);
		if (!result.second) {
			entries[i].weight += (**(result.first)).weight;
			entries[i].eps += (**(result.first)).eps;
			tempset->erase(result.first);
			tempset->insert(&entries[i]);
		}
	}
	ENTRY_P_SET::iterator it;
	for (it = tempset->begin(); it != tempset->end(); it++) {
		summary->push_back(**it);
	}
	delete tempset;
	sort(summary->begin(), summary->end(), sort_reverse);
	summary->resize(k);
}

/***
 * Merges the items of two summaries and keeps top k elements
 * @param sum1 pointer to summary of type vector<Entry>
 * @param resvbuf pointer to summary of type Entry[]
 * @param sumsize number of elements of the merged summaries
 */
static void merge_parallel(ENTRY_VEC * sum1, Entry recvbuf[], int sumsize) {
	//fprintf(stderr, "%s\n", "called merge_parallel");
	int sum1size = sumsize;
	int sum2size = sumsize;

	sort(sum1->begin(), sum1->end(), sort_by_key);
	sort(recvbuf, recvbuf + sumsize, sort_by_key);
	int i, j = 0;
	while (i < sum1->size() and j < sum2size) {
		if (sum1->at(i) == recvbuf[j]) {
			sum1->at(i).weight += recvbuf[j].weight;
			sum1->at(i).eps += recvbuf[j].eps;
			i++;
			j++;
		} else {
			sum1->push_back(recvbuf[j]);
			j++;
		}
	}
	sort(sum1->begin(), sum1->end(), sort_reverse);
	sum1->resize(min(sumsize, (int) sum1->size()));
	/*sort(sum1->begin(), sum2->end(), sort_reverse);
	 */

	/* Sort both vectors
	 * keep 2 pointers starting at begin()
	 * merge by updating entries in one
	 * sort merged reverse
	 * keep top k
	 *
	 **/
}

static void FLOATVEC_TYPE(MPI_Datatype * datatype) {
	FLOAT_VEC vec(1);
	MPI_Datatype types[1] = { MPI_FLOAT };
	int blocklen[1];
	MPI_Aint disp[1];
	MPI_Aint base;
	MPI_Get_address(&vec, &base);
	MPI_Get_address(&vec[0], &disp[0]);
	disp[0] = disp[0] - base;
	MPI_Type_create_struct(1, blocklen, disp, types, datatype);
	MPI_Type_commit(datatype);

}
static void HEADER_TYPE(Header *header, MPI_Datatype *datatype) {
	MPI_Datatype types[2] = { MPI_INT, MPI_INT };
	int blocklen[2] = { 1, 1 };
	MPI_Aint disp[2];
	MPI_Aint base;
	MPI_Get_address(header, &base);
	MPI_Get_address(&(*header).TAG, &disp[0]);
	MPI_Get_address(&(*header).count, &disp[1]);
	disp[0] = disp[0] - base;
	disp[1] = disp[1] - base;
	MPI_Type_create_struct(2, blocklen, disp, types, datatype);
	MPI_Type_commit(datatype);

}

void readheader(char* filename, DATA_header * header) {
	MPI_File fh;
	MPI_Status status;

	int rc = MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_RDONLY,
			MPI_INFO_NULL, &fh);
	if (rc) {
		printf("Unable to open file\n");
		fflush (stdout);
		MPI_File_close(&fh);
	}
	printf("%s\n", "Opened MPIFIle");
	printf("%s\n", "Called readheader");
	int * _header = (int*) malloc(4 * sizeof(int));

	MPI_File_read(fh, _header, 4, MPI_INT, &status);
	header->JC_SIZE = _header[0];
	printf("JC_SIZE %d\n", _header[0]);
	header->JC = (int*) malloc(_header[0] * sizeof(int));
	header->CP = (int*) malloc(_header[1] * sizeof(int));
	MPI_File_read(fh, header->JC, _header[0], MPI_INT, &status);
	MPI_File_read(fh, header->CP, _header[1], MPI_INT, &status);
	header->COUNTS = (int*) malloc(header->JC_SIZE * sizeof(int));
	for (int i = 0; i < _header[0]; i++) {
		header->COUNTS[i] = header->CP[i + 1] - header->CP[i];
	}
	MPI_Free_mem(_header);
	MPI_File_close(&fh);
	printf("%s\n", "Completed  init()");
}

/***
 * Parser for command arguments
 */
struct CmdParser {
	cmdline::parser a;
	CmdParser() {
		a.add < string > (INFILE, 'i', "input file", true, "");
		a.add < string
				> (FORMAT, 'f', "file format", true, "", cmdline::oneof < string
						> ("dcs", "coo", "txt"));
		a.add<int>(KSIZE, 'k', "summary size", true, 2);
		a.add < string
				> (STYPE, 's', "summary type", false, "buc", cmdline::oneof
						< string > ("buf", "buc"));
		a.add("help", 0, "print usage");

	}

	bool parseargs(int argc, char *argv[]) {
		bool ok = a.parse(argc, argv);
		if (argc == 1 or a.exist("help") or !ok) {
			//printerror();
			cerr << a.usage();
		}

		return ok;
	}

	string get_cmdargs() {
		stringstream ss;
		ss << "\nINFILE: " << a.get < string > (INFILE) << "\n";
		ss << "KSIZE: " << a.get<int>(KSIZE) << "\n";
		ss << "STYPE: " << a.get < string > (STYPE) << "\n";
		ss << "FORMAT: " << a.get < string > (FORMAT) << "\n";
		return ss.str();

	}

	void printargs() {
		cerr << "\nINFILE: " << a.get < string > (INFILE) << endl;
		cerr << "KSIZE: " << a.get<int>(KSIZE) << endl;
		cerr << "STYPE: " << a.get < string > (STYPE) << endl;
		cerr << "FORMAT: " << a.get < string > (FORMAT) << endl;
	}

	void printerror() {
		cerr
				<< "\n*****************************************************\nIncorrect arguments. Please read the usage message\n*****************************************************\n";
	}
};
